import math, os, sys, poser, random

os.chdir( os.path.dirname(poser.AppLocation()) )
sys.path.append(os.path.join("Runtime","Python","Poseworks","Particles3","data"))

from prt3Constants import *
from prt3Math import *
import prt3Core

def ageParticleGroup(emitter, group, end=False):
	e = emitter.parameters
	g = group.parameters
	recycle = g.getRecycle()
	lifespan = g.getLifespan()
	# Array to hold particles that can be spawned
	available = []
	for particle in group.particles:
		p = particle.parameters
		particle.key()
		# Get the particle's age (in frames)
		age = p.getLife() + 1
		if age > lifespan:
			# Kill the particle if it has lived long enough
			# and then add it to the available list if
			# recycling is enabled
			if recycle:
				particle.kill()
				available.append(particle)
			else:
				p.setScale(0.0,0.0,0.0,0.0)
		elif age == 0:
			# Particle is not yet spawned, and therefore
			# should be added to available list
			available.append(particle)
		else:
			# Record the particle's new age
			p.setLife(age)
			# Particle is already spawned and needs to be updated
			updateParticle(particle, emitter, group)
	spawnThisMany = ifelse(end, 0, g.getRateOfEmission())
	# If there are particles available to spawn, spawn them until
	# the needs of the emitter have been satisfied or until
	# you run out of particles.
	for particle in available:
		if spawnThisMany == 0:
			break
		else:
			spawnThisMany = spawnThisMany - 1
			particle.spawn(emitter, group)
			updateParticle(particle, emitter, group)

def updateParticle(particle, emitter, group):
	p = particle.parameters
	e = emitter.parameters
	g = group.parameters
	age = p.getLife()
	fps = poser.Scene().FramesPerSecond()
	origin = getWorldPoint(particle)
	# Set the point
	newPoint = particle.spawnSpace.inverse() * particle.localPoint
	# Move the point
	newPoint = newPoint + particle.spawnYrot * particle.spawnXrot * particle.velocity.getUnmultiplied(age) / fps
	# Orbit the point around the local-space spawn point (0,0,0)
	orbit = particle.orbit.getUnmultiplied(age)
	orbitx, orbity, orbitz = orbit.x, orbit.y, orbit.z
	newPoint = rotation(ZAXIS, orbitz) * rotation(YAXIS, orbity) * rotation(XAXIS, orbitx) * newPoint
	# Transform the point into world space
	newPoint = particle.spawnSpace * newPoint
	# Figure out attractor influence
	for attractor in g_aATTRACTORS:
		#print "Old", newPoint
		newPoint = attract(attractor, newPoint)
		#print "New", newPoint
	# Store the local point value
	particle.localPoint = translation(-particle.spawnPoint.x,
									  -particle.spawnPoint.y,
									  -particle.spawnPoint.z) * newPoint
	#print "New local", particle.localPoint
	# Gravity
	gravity = 0.5 * (3.739 * g.getGravity() / fps) * (age*age)
	# Move the particle
	p.setTran(newPoint.x, newPoint.y - gravity, newPoint.z)
	# Deflect
	"""
	destination = getWorldPoint(particle)
	for deflector in g_aDEFLECTORS:
		deflect(deflector, particle, origin, destination)
	"""
	# Calculate spin
	newRot = particle.localRotation + particle.spin.getUnmultiplied(age)
	particle.localRotation = newRot
	# Orient the particle along its spawn vector
	newRot = particle.spawnAngle + newRot + particle.spawnRotation
	# Assign rotation values
	p.setRot(newRot.x, newRot.y, newRot.z)
	# Calculate grow
	#particle.localScale = particle.localScale + particle.grow.get(age)
	particle.localScale = particle.localScale + particle.grow.getUnmultiplied(age)
	p.setScaleMod(particle.localScale.x, particle.localScale.y, particle.localScale.z)
	#particle.localScale = particle.localScale + particle.grow.getUnmultiplied(age)
	#p.setScale(1.0, particle.localScale.x, particle.localScale.y, particle.localScale.z)

def attract(attractor, point):
	distance = attractor.getOrigin().dist(point)
	attrRange = attractor.getRange()
	attrStrength = attractor.getStrength() / 10
	if distance > attrRange or attrRange == 0 or attrStrength == 0 or distance == 0:
		return point
	else:
		"""
		attrFalloff = attractor.getFalloff()
		sway = distance/attrRange * attrStrength * (attrFalloff) ** (distance/attrRange)
		print distance, sway
		return sway * attractor.getOrigin() + point
		"""
		#return attractor.getOrigin().blend(point, attrStrength * (1.0 - distance/attrRange))
		attrVec = (attractor.getOrigin() - point).normalize()
		proximity = distance / attrRange
		return point + attrVec * attrStrength * proximity * ((1.0 - attractor.getFalloff()) ** proximity)

def deflect(deflector, particle, p0, p1):
	worldSpace = deflector.getObjectSpace()
	deflectorPos = getWorldPoint(deflector)
	# Transform the points into local space
	P0 = worldSpace.inverse() * p0
	P1 = worldSpace.inverse() * p1
	# Intersect the segment created by the points with the local plane
	u = (P1 - P0).normalize()
	w = (P0 - PwPoint(0,0,0)).normalize()
	Pn = PwVector(0,1,0)
	D = Pn.dot(u)
	N = -Pn.dot(w)
	# Test if segment is on the plane
	if math.fabs(D) < 0.0001:
		return False
	# Test if there is no intersection
	sI = N / D
	if sI < 0 or sI > 1:
		return False
	# Otherwise, find point of intersection
	I0 = P0 + sI * u
	# If the point of intersection is out of bounds, stop
	radius = 0.382 * deflector.getSize()
	if I0.x > radius or I0.x < -radius or I0.z > radius or I0.z < -radius:
		return False
	# Get the distance left to travel
	remainder = P0.dist(P1) - P0.dist(I0)
	# Get the dampening
	dampening = 1.0 - deflector.getDampening()
	# Find the reflected vector
	R = (u - 2 * u.dot(Pn) * Pn).normalize()
	# Move the point along the reflected vector
	I1 = I0 + dampening * remainder * R
	# Transform the point back into world space
	I1 = worldSpace * I1
	# Return the dampening, revised spawning vector and the new point
	#particle.spawnPoint = I1
	#particle.spawnVector = (IDENTITY4x4, IDENTITY4x4, R, worldSpace * R)
	#particle.spawnSpace = worldSpace.inverse()
	particle.parameters.setTran(I1.x, I1.y, I1.z)
	particle.velocity = particle.velocity * dampening
	particle.orbit = particle.orbit * dampening
	particle.spin = particle.spin * dampening
	particle.spawnPoint = translation(-deflectorPos.x, -deflectorPos.y, -deflectorPos.z) * worldSpace * I0
	particle.spawnVector = (particle.spawnVector[0], particle.spawnVector[1], particle.spawnVector[2], R)
	return True

def getWorldPoint(obj):
	return PwPoint(obj.source.WorldDisplacement())

def simulateFrame(system):
	# Go through particle groups
	for group in system.particlegroups:
		# Get number of particles to spawn this frame
		# Advance the age of the particles in the system
		ageParticleGroup(system.emitter, group)

def simulate(system, inplace=0, update=None):
	global g_aATTRACTORS, g_aDEFLECTORS
	g_aATTRACTORS = prt3Core.getAttractors()
	g_aDEFLECTORS = prt3Core.getDeflectors()
	scene = poser.Scene()
	for group in system.particlegroups:
		for frame in range(scene.NumFrames()):
			#scene.SetFrame(frame)
			group.unkeyAll(frame)
		# Fresh start
		scene.SetFrame(int(0))
		group.killAll()
		if group.parameters.getShape() == SHAPE_SPRITE:
			for particle in group.particles:
				particle.source.PointAt(scene.CurrentCamera())
		# Loop over particle range
		start = group.parameters.getStart() - 1
		end = group.parameters.getEnd() - 1
		if end < 0 or end < start:
			end = scene.NumFrames() - 1
		# Kill all particles before the simulation range
		if start >= 0:
			current = 0
			while current <= start:
				scene.SetFrame(int(current))
				group.killAll()
				current = current + 1
		# Simulate each frame inside the simulation range
		current = int(start)
		maxinplace = inplace
		while current >= start and current <= end:
				if update:
					update("Simulating %s - Frame %i" % (system.name, current + 1))
				scene.SetFrame(int(current))
				while inplace > 0:
					if update:
						update("Simulating %s - In Place (Step %i of %i)" % (system.name, maxinplace - inplace, maxinplace))
					ageParticleGroup(system.emitter, group)
					inplace = inplace - 1
				ageParticleGroup(system.emitter, group)
				current = current + 1
				#if current % 6 == 0:
				#	 scene.Draw()
		# Kill all particles after the simulation range
		if end >= 0 and end > start:
			current = end + 1
			while current < scene.NumFrames():
				scene.SetFrame(int(current))
				#group.killAll()
				ageParticleGroup(system.emitter, group, end=True)
				current = current + 1
	#simulateFrame(system)

if __name__ == '__main__':
	system = prt3Core.buildSystemFrom(poser.Scene().CurrentFigure())
	"""
	print system.source
	print system.particlegroups[0].source
	print system.emitter.source
	for o in system.particlegroups[0].particles:
		print o.source
	"""
	simulate(system, inplace=0)
	"""
	ps = []
	point = PwPoint(10,0,0)
	from Tkinter import *
	root = Tk()
	root.geometry("800x800")
	can = Canvas(root, width=800, height=800)
	can.create_rectangle(399, 399, 401, 401)
	for i in range(90):
		newpoint = rotation(YAXIS, -15 * i) * (point + PwVector(20, 0, 0) * i)
		can.create_rectangle(newpoint.x + 398, newpoint.z + 398, newpoint.x + 402, newpoint.z + 402)
	can.create_text(400, 400, anchor=NW, text="Moooo")
	can.grid(row=0,column=0)
	root.mainloop()
	"""
